package club.kingon.sql.builder.function;

import club.kingon.sql.builder.LMDFunction;
import club.kingon.sql.builder.entry.Column;
import club.kingon.sql.builder.util.ConditionUtils;
import club.kingon.sql.builder.util.LambdaUtils;

import java.util.Arrays;
import java.util.stream.Collectors;

/**
 * todo how to implements
 * @author dragons
 * @date 2021/11/16 19:17
 */
public abstract class Fcs {

    /**
     * sql count() function
     * like:
     *  Functions.count(column) to count(column)
     * @param c column name
     * @return countSql
     */
    public static String count(String c) {
        if (c == null || c.length() == 0) {
            throw new IllegalArgumentException("count function column name must be not empty.");
        }
        return "count(" + c + ")";
    }

    /**
     * sql count() function
     * like:
     *  Functions.count(column) to count(column)
     * @param c column name
     * @return countSql
     */
    public static String count(Column c) {
        if (c == null) {
            throw new IllegalArgumentException("count function column name must be not null.");
        }
        return "count(" + c.getName() + ")";
    }

    /**
     * sql count() function
     * like:
     *  Functions.count(column) to count(column)
     * @param c column name
     * @return countSql
     */
    public static <T> String count(LMDFunction<T, ?> c) {
        if (c == null) {
            throw new IllegalArgumentException("count function column name must be not null.");
        }
        return "count(" + LambdaUtils.getColumnName(c) + ")";
    }

    /**
     * sql count(1)
     * @return count(1) sql
     */
    public static String count1() {
        return "count(1)";
    }

    /**
     * sql count(*)
     */
    public static String countAll() {
        return "count(*)";
    }

    /**
     * sql concat()
     * @param strOrColumns String or {@link LMDFunction} or {@link Column} type, other type will be ignored. String type is treated as a constant string rather than a field name
     * @return concat sql
     */
    public static String concat(Object ...strOrColumns) {
        return concat(false, strOrColumns);
    }

    /**
     * sql concat()
     * @param columns String, {@link LMDFunction} or {@link Column} type, other type will be ignored.
     * @return concat sql
     */
    public static String concatColumn(Object ...columns) {
        return concat(true, columns);
    }

    /**
     * sql substring()
     * @param strOrColumn String or {@link LMDFunction} or {@link Column} type, other type will be ignored. String type is treated as a constant string rather than a field name
     * @param start start
     * @param length length
     * @return substring sql
     */
    public static String substring(Object strOrColumn, int start, int length) {
        return "substring(" + resolveStringFunctionArgs(false, strOrColumn) + ", " + start + ", " + length + ")";
    }

    /**
     * sql substring()
     * @param column String or {@link LMDFunction} or {@link Column} type, other type will be ignored.
     * @param start start
     * @param length length
     * @return substring sql
     */
    public static String substringColumn(Object column, int start, int length) {
        return "substring(" + resolveStringFunctionArgs(true, column) + ", " + start + ", " + length + ")";
    }

    /**
     * sql substr()
     * @param strOrColumn String or {@link LMDFunction} or {@link Column} type, other type will be ignored. String type is treated as a constant string rather than a field name
     * @param start start
     * @param length length
     * @return substr sql
     */
    public static String substr(Object strOrColumn, int start, int length) {
        return "substr(" + resolveStringFunctionArgs(false, strOrColumn) + ", " + start + ", " + length + ")";
    }

    /**
     * sql substr()
     * @param column String or {@link LMDFunction} or {@link Column} type, other type will be ignored.
     * @param start start
     * @param length length
     * @return substr sql
     */
    public static String substrColumn(Object column, int start, int length) {
        return "substr(" + resolveStringFunctionArgs(true, column) + ", " + start + ", " + length + ")";
    }


    /**
     * sql substring()
     * @param strOrColumn String or {@link LMDFunction} or {@link Column} type, other type will be ignored. String type is treated as a constant string rather than a field name
     * @param start start
     * @return substring sql
     */
    public static String substring(Object strOrColumn, int start) {
        return "substring(" + resolveStringFunctionArgs(false, strOrColumn) + ", " + start + ")";
    }

    /**
     * sql substring()
     * @param column String or {@link LMDFunction} or {@link Column} type, other type will be ignored.
     * @param start start
     * @return substring sql
     */
    public static String substringColumn(Object column, int start) {
        return "substring(" + resolveStringFunctionArgs(true, column) + ", " + start + ")";
    }

    /**
     * sql substr()
     * @param strOrColumn String or {@link LMDFunction} or {@link Column} type, other type will be ignored. String type is treated as a constant string rather than a field name
     * @param start start
     * @return substr sql
     */
    public static String substr(Object strOrColumn, int start) {
        return "substr(" + resolveStringFunctionArgs(false, strOrColumn) + ", " + start + ")";
    }

    /**
     * sql substr()
     * @param column String or {@link LMDFunction} or {@link Column} type, other type will be ignored.
     * @param start start
     * @return substr sql
     */
    public static String substrColumn(Object column, int start) {
        return "substr(" + resolveStringFunctionArgs(true, column) + ", " + start + ")";
    }

    /**
     * sql upper()
     * @param strOrColumn String or {@link LMDFunction} or {@link Column} type, other type will be ignored. String type is treated as a constant string rather than a field name
     * @return upper sql
     */
    public static String upper(Object strOrColumn) {
        return "upper(" + resolveStringFunctionArgs(false, strOrColumn) + ")";
    }

    /**
     * sql lower()
     * @param strOrColumn String or {@link LMDFunction} or {@link Column} type, other type will be ignored. String type is treated as a constant string rather than a field name
     * @return lower sql
     */
    public static String lower(Object strOrColumn) {
        return "lower(" + resolveStringFunctionArgs(false, strOrColumn) + ")";
    }


    /**
     * sql upper()
     * @param column String or {@link LMDFunction} or {@link Column} type, other type will be ignored.
     * @return upper sql
     */
    public static String upperColumn(Object column) {
        return "upper(" + resolveStringFunctionArgs(true, column) + ")";
    }

    /**
     * sql lower()
     * @param column String or {@link LMDFunction} or {@link Column} type, other type will be ignored.
     * @return lower sql
     */
    public static String lowerColumn(Object column) {
        return "lower(" + resolveStringFunctionArgs(true, column) + ")";
    }

    /**
     * sql length()
     * @param strOrColumn String or {@link LMDFunction} or {@link Column} type, other type will be ignored. String type is treated as a constant string rather than a field name
     * @return length sql
     */
    public static String length(Object strOrColumn) {
        return "length(" + resolveStringFunctionArgs(false, strOrColumn) + ")";
    }

    /**
     * sql length()
     * @param column String or {@link LMDFunction} or {@link Column} type, other type will be ignored.
     * @return length sql
     */
    public static String lengthColumn(Object column) {
        return "length(" + resolveStringFunctionArgs(true, column) + ")";
    }


    /**
     * sql trim()
     * @param strOrColumn String or {@link LMDFunction} or {@link Column} type, other type will be ignored. String type is treated as a constant string rather than a field name
     * @return trim sql
     */
    public static String trim(Object strOrColumn) {
        return "trim(" + resolveStringFunctionArgs(false, strOrColumn) + ")";
    }

    /**
     * sql trim()
     * @param column String or {@link LMDFunction} or {@link Column} type, other type will be ignored.
     * @return trim sql
     */
    public static String trimColumn(Object column) {
        return "trim(" + resolveStringFunctionArgs(true, column) + ")";
    }

    private static String concat(boolean strIsColumn, Object ...args) {
        if (args.length == 0) {
            throw new IllegalArgumentException("concat function must have at least one parameter.");
        }
        String content = Arrays.stream(args).filter(e -> e instanceof Character || e instanceof Column || e instanceof LMDFunction)
                .map(e -> resolveStringFunctionArgs(strIsColumn, e))
                .collect(Collectors.joining(", "));
        if ("".equals(content)) {
            throw new IllegalArgumentException("concat function must have at least one parameter. And parameters' type should be in (String, Column).");
        }
        return "concat(" + content + ")";
    }

    private static String resolveStringFunctionArgs(boolean strIsColumn, Object arg) {
        if (arg instanceof Character) {
            if (strIsColumn) return arg.toString();
            return ConditionUtils.parseValue(arg);
        } else if (arg instanceof LMDFunction) {
            return LambdaUtils.getColumnName((LMDFunction) arg);
        } else if (arg instanceof Column) {
            return ((Column) arg).getName();
        }
        throw new IllegalArgumentException("unsupported resolve column type [" + arg.getClass().getName() + "].");
    }
}
